import type { AppointmentManagement } from '@shared/types/'
import { normalizeAppointmentTime } from '@shared/utils/appointment-time'
import { ipcMain } from 'electron'
import { v4 as uuidv4 } from 'uuid'
import { getBookingMonitor } from '../booking/booking-monitor'
import { getUnifiedBookingService } from '../booking/booking-service'
import { cancelAllEngines, clearGlobalCancel, clearRegistry, getActiveEngineCount, isGlobalCancelRequested, requestGlobalCancel } from '../booking/engine-registry'
import { getMonitoringTaskExecutor } from '../booking/monitoring-task-executor'
import * as limiter from '../booking/session-limiter'
import { getConfig } from '../config/index'
import { getDatabase } from '../db/index'
import { getLogger } from '../logger/index'
import { UserLogger } from '../logger/user-logger'
import { getAppointmentService } from '../services/appointment-service'
import { getAutomationService } from '../services/automation-service'
import { getTdabsRequestService } from '../services/tdabs-request-service'

export function setupBookingHandlers(): void {
  const logger = getLogger()
  const svc = getUnifiedBookingService()
  let activeBatchJobId: string | null = null
  let isStopping = false

  ipcMain.handle('booking:book', async (_event, payload: { mode: 'playwright' | 'request', appointment: AppointmentManagement, options?: any, persist?: boolean }) => {
    try {
      const prisma = getDatabase()
      const singleJobId = uuidv4()
      const now = Math.floor(Date.now() / 1000)
      try {
        await prisma.job.create({
          data: {
            id: singleJobId,
            type: 'booking',
            mode: payload.mode,
            status: 'running',
            progress: 5,
            current_step: '开始预约',
            user_info: JSON.stringify({ id: payload.appointment.id, name: payload.appointment.full_name }),
            options: JSON.stringify(payload.options || {}),
            created_at: now,
            updated_at: now,
            started_at: now,
            result: JSON.stringify({ logs: [{ timestamp: now, phase: 'start', step: '开始预约', message: '任务创建' }] }),
          },
        })
        logger.info('job.created', { id: singleJobId, type: 'booking' })
      }
      catch (e) {
        logger.warn('创建 booking job 失败', { error: String((e as any)?.message || e) })
      }
      // 包装 options，接收进度回调，使用 UserLogger 写入用户友好日志
      const optionsWithProgress = {
        ...(payload.options || {}),
        jobId: singleJobId, // 传递 jobId 供 UserLogger 使用
        onProgress: async ({ progress, step, timestamp }: { progress: number, step: string, timestamp: number }) => {
          // 使用 UserLogger 记录用户友好的日志
          await UserLogger.step(singleJobId, step, progress)

          // 同时更新进度（保留原有逻辑）
          try {
            await prisma.job.update({
              where: { id: singleJobId },
              data: {
                progress,
                current_step: step,
                updated_at: timestamp,
              },
            })
          }
          catch {}
        },
      }

      // 若全局停止已请求，则不再启动新的单次预约
      try {
        if (isGlobalCancelRequested()) {
          try {
            const ts = Math.floor(Date.now() / 1000)
            const row = await prisma.job.findUnique({ where: { id: singleJobId }, select: { result: true } })
            const prev = row?.result ? JSON.parse(row.result as any) : {}
            const logs = Array.isArray(prev.logs) ? prev.logs : []
            logs.push({ timestamp: ts, phase: 'warn', step: '已取消', message: '处于停止中，不再启动新预约' })
            await prisma.job.update({ where: { id: singleJobId }, data: { status: 'cancelled', updated_at: ts, completed_at: ts, current_step: '已取消', result: JSON.stringify({ ...prev, logs }) } })
          }
          catch {}
          return { success: false, error: '预约已被停止' }
        }
      }
      catch {}

      const result = await svc.bookWith(payload.mode, payload.appointment, optionsWithProgress)

      // 可选：成功后落库
      try {
        if (payload.persist) {
          const appSvc = getAppointmentService()
          if (result.success) {
            // 构建更新数据，只在有值时才更新日期和时间
            const updateData: any = {
              appointment_status: 'booked',
              reference_number: result.referenceNumber,
            }
            // 只在有值时才更新日期和时间
            if (result.selectedDate) {
              updateData.appointment_date = result.selectedDate
            }
            if (result.selectedTimeslot) {
              updateData.appointment_time = normalizeAppointmentTime(result.selectedTimeslot)
            }
            // 若缺少参考编号，尝试按证件/执照查询一次以补齐（同步快速兜底）
            if (!updateData.reference_number || !String(updateData.reference_number).trim()) {
              try {
                const tsvc = getTdabsRequestService()
                const byId = await tsvc.queryById({
                  passport: payload.appointment.passport_number || undefined,
                  license: payload.appointment.license_number || undefined,
                })
                if (byId?.success && byId.data?.parsed) {
                  const info = byId.data.parsed
                  if (info.reference_number)
                    updateData.reference_number = info.reference_number
                  if (!updateData.appointment_date && info.appointment_date)
                    updateData.appointment_date = info.appointment_date
                  if (!updateData.appointment_time && info.appointment_time)
                    updateData.appointment_time = normalizeAppointmentTime(info.appointment_time)
                }
              }
              catch {}
            }
            await appSvc.updateAppointment(payload.appointment.id, updateData)
            // 校验写库是否生效，若未生效则补救一次
            try {
              const fresh = await appSvc.getAppointment(payload.appointment.id)
              if (!(fresh && fresh.appointment_status === 'booked')) {
                await appSvc.updateAppointment(payload.appointment.id, updateData)
              }
            }
            catch {}
          }
          else {
            // 特判：若站点提示"已存在同服务类型的预约（512-E-0001）"，视为已预约，直接标记为 booked，避免反复重试
            const alreadyBooked = typeof result.error === 'string' && /512-E-0001|已存在同服务类型的预约|already\s*book/i.test(result.error)
            if (alreadyBooked) {
              await appSvc.updateAppointment(payload.appointment.id, { appointment_status: 'booked', error_message: result.error || '', retry_count: 0 } as any)
            }
            else {
              // 获取当前预约记录以读取重试次数和错误类型
              const currentAppointment = await appSvc.getAppointment(payload.appointment.id)
              const currentRetryCount = currentAppointment?.retry_count || 0
              const currentErrorType = currentAppointment?.error_message || ''

              // 判断错误类型
              const isTimeslotFull = typeof result.error === 'string' && /timeslot_full|时段已满|時段已滿|时间段已满|時間段已滿|slot.*full|no.*slot|无可用时段|無可用時段|已满|512-e-0003|512-E-0003/i.test(result.error)
              const isDuplicateLogin = typeof result.error === 'string' && /duplicate_login|重複登入|重覆登入|重复登入|512-e-0044|512-E-0044|系統而中斷|系统而中断/i.test(result.error)
              const isPreferredDateMissed = typeof result.error === 'string' && /未命中偏好日期|未命中首选日期|preferred.*date.*miss/i.test(result.error)
              const isNoAvailableDate = typeof result.error === 'string' && /70天内无可用日期|无可用日期|没有可用.*日期|no.*available.*date|当前没有可用的预约日期/i.test(result.error)
              const isSystemBusy = typeof result.error === 'string' && /(系统繁忙|系統繁忙|server\s*busy)/i.test(result.error)
              // 判断是否为网络/代理错误（应该重试，但连续3次后停止）
              const isNetworkError = typeof result.error === 'string' && (
                /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|ENETUNREACH|timeout|TIMEOUT|网络错误|网络异常|connection.*reset|connection.*refused|connection.*timeout|connect.*timeout|read.*ECONNRESET|Request timeout|Client\s+network\s+socket\s+disconnected\s+before\s+secure\s+TLS\s+connection\s+was\s+established|socket\s+hang\s+up|EPIPE|ERR_SSL/i.test(result.error)
              )

              // 检查是否连续出现相同错误类型
              const isNoAvailableDateConsecutive = isNoAvailableDate && /无可用日期|no.*available.*date/i.test(currentErrorType)
              const isNetworkErrorConsecutive = isNetworkError && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|timeout|网络错误|网络异常|socket\s+hang\s+up|EPIPE|ERR_SSL/i.test(currentErrorType)
              const isSystemBusyConsecutive = isSystemBusy && /(系统繁忙|系統繁忙|server\s*busy)/i.test(currentErrorType)

              // 对于无可用日期：连续3次后停止
              if (isNoAvailableDate) {
                const noDateRetryCount = isNoAvailableDateConsecutive ? currentRetryCount + 1 : 1
                if (noDateRetryCount >= 3) {
                  // 连续3次无可用日期，标记为failed
                  await appSvc.updateAppointment(payload.appointment.id, {
                    appointment_status: 'failed',
                    error_message: result.error || '无可用日期（连续3次）',
                    retry_count: noDateRetryCount,
                  } as any)
                  logger.info('预约无可用日期（连续3次），标记为failed', { id: payload.appointment.id, error: result.error, retryCount: noDateRetryCount })
                }
                else {
                  // 继续重试
                  await appSvc.updateAppointment(payload.appointment.id, {
                    appointment_status: 'pending',
                    error_message: result.error || '无可用日期（等待60秒后重试）',
                    retry_count: noDateRetryCount,
                    // 渲染层倒计时用（不入库，服务层将仅广播该字段）
                    next_retry_at: new Date(Date.now() + 60_000),
                  } as any)
                  logger.info('预约无可用日期，保持pending状态以便重试（等待60秒）', { id: payload.appointment.id, error: result.error, retryCount: noDateRetryCount })
                }
              }
              // 对于网络/代理错误：连续3次后停止
              else if (isNetworkError) {
                const networkRetryCount = isNetworkErrorConsecutive ? currentRetryCount + 1 : 1
                if (networkRetryCount >= 3) {
                  // 连续3次网络错误，标记为failed
                  await appSvc.updateAppointment(payload.appointment.id, {
                    appointment_status: 'failed',
                    error_message: result.error || '网络错误（连续3次）',
                    retry_count: networkRetryCount,
                  } as any)
                  logger.info('预约失败（网络错误连续3次），标记为failed', { id: payload.appointment.id, error: result.error, retryCount: networkRetryCount })
                }
                else {
                  // 继续重试
                  await appSvc.updateAppointment(payload.appointment.id, {
                    appointment_status: 'pending',
                    error_message: result.error || '网络错误',
                    retry_count: networkRetryCount,
                  } as any)
                  logger.info('预约失败（网络错误），保持pending状态以便重试', { id: payload.appointment.id, error: result.error, retryCount: networkRetryCount })
                }
              }
              // 系统繁忙：连续3次后停止
              else if (isSystemBusy) {
                const busyRetryCount = isSystemBusyConsecutive ? currentRetryCount + 1 : 1
                if (busyRetryCount >= 3) {
                  await appSvc.updateAppointment(payload.appointment.id, {
                    appointment_status: 'failed',
                    error_message: result.error || '系统繁忙（连续3次）',
                    retry_count: busyRetryCount,
                  } as any)
                  logger.info('预约失败（系统繁忙连续3次），标记为failed', { id: payload.appointment.id, error: result.error, retryCount: busyRetryCount })
                }
                else {
                  await appSvc.updateAppointment(payload.appointment.id, {
                    appointment_status: 'pending',
                    error_message: result.error || '系统繁忙',
                    retry_count: busyRetryCount,
                  } as any)
                  logger.info('预约失败（系统繁忙），保持pending状态以便重试', { id: payload.appointment.id, error: result.error, retryCount: busyRetryCount })
                }
              }
              // 时间段已满：始终重试
              else if (isTimeslotFull) {
                await appSvc.updateAppointment(payload.appointment.id, {
                  appointment_status: 'pending',
                  error_message: result.error || '时间段已满',
                  retry_count: 0, // 时间段已满不计数，因为这是正常的业务逻辑
                } as any)
                logger.info('预约失败（时间段已满），保持pending状态以便重试', { id: payload.appointment.id, error: result.error })
              }
              // 重复登录/系统中断：始终重试
              else if (isDuplicateLogin) {
                await appSvc.updateAppointment(payload.appointment.id, {
                  appointment_status: 'pending',
                  error_message: result.error || '服務因使用者重覆登入系統而中斷',
                  retry_count: 0, // 重复登录不计数，因为这是临时性的系统限制
                } as any)
                logger.info('预约失败（重复登录/系统中断），保持pending状态以便重试', { id: payload.appointment.id, error: result.error })
              }
              // 未命中偏好日期：始终重试
              else if (isPreferredDateMissed) {
                await appSvc.updateAppointment(payload.appointment.id, {
                  appointment_status: 'pending',
                  error_message: result.error || '未命中偏好日期',
                  retry_count: 0, // 未命中偏好日期不计数
                } as any)
                logger.info('预约未命中偏好日期，保持pending状态以便重试', { id: payload.appointment.id, error: result.error })
              }
              // 其他错误：标记为failed
              else {
                await appSvc.updateAppointment(payload.appointment.id, {
                  appointment_status: 'failed',
                  error_message: result.error || 'booking failed',
                  retry_count: currentRetryCount,
                } as any)
                logger.info('预约失败（其他错误），标记为failed', { id: payload.appointment.id, error: result.error })
              }
            }
          }
        }
      }
      catch (e) {
        logger.warn('booking:book persist failed', { error: String((e as any)?.message || e) })
      }

      // 更新单次预约 Job 状态
      try {
        const end = Math.floor(Date.now() / 1000)
        // 从策略返回的 outcome 中取流量统计（更可靠）
        const traffic = (result as any)?.traffic
        // 合并已有日志
        const row = await prisma.job.findUnique({ where: { id: singleJobId }, select: { result: true } })
        const prev = row?.result ? JSON.parse(row.result as any) : {}
        const logs = Array.isArray(prev.logs) ? prev.logs : []
        logs.push({ timestamp: end, phase: result.success ? 'success' : 'error', step: result.success ? '预约成功' : '预约失败', message: result.success ? (result.referenceNumber || '') : (result.error || 'booking failed') })

        // 补充：预约信息与指标（对齐历史记录页的信息口径）
        const appointmentInfo = {
          user_passport: payload.appointment.passport_number,
          user_license: payload.appointment.license_number,
          user_name: payload.appointment.full_name,
          user_email: payload.appointment.email,
          user_phone: payload.appointment.phone_number || '',
          issuing_country: payload.appointment.issuing_country,
          issuing_authority: payload.appointment.issuing_authority,
          service_type: payload.appointment.service_type,
          office_code: payload.appointment.office_code,
          booking_mode: payload.mode === 'request' ? 'quick' : 'standard',
          appointment_date: (result as any)?.selectedDate || '',
          appointment_time: (result as any)?.selectedTimeslot || '',
        }
        const metrics = {
          durationMs: Math.max(0, (end - now) * 1000),
          bytesSent: Number((traffic?.totalUp) || 0),
          bytesReceived: Number((traffic?.totalDown) || 0),
        }

        await prisma.job.update({
          where: { id: singleJobId },
          data: {
            status: result.success ? 'success' : 'failed',
            progress: 100,
            current_step: result.success ? '预约成功' : '预约失败',
            updated_at: end,
            completed_at: end,
            ...(result.success
              ? { result: JSON.stringify({ reference_number: result.referenceNumber, selected_date: result.selectedDate, selected_timeslot: result.selectedTimeslot, appointment_info: appointmentInfo, metrics, logs, traffic }) }
              : { error_message: result.error || 'booking failed', result: JSON.stringify({ appointment_info: appointmentInfo, metrics, logs, traffic }) }),
          },
        })
        logger.info('job.updated', { id: singleJobId, traffic_totalUp: traffic?.totalUp || 0, traffic_totalDown: traffic?.totalDown || 0 })
      }
      catch (e) {
        logger.warn('更新 booking job 失败', { error: String((e as any)?.message || e) })
      }

      return { success: result.success, data: { ...result, jobId: singleJobId }, error: result.error }
    }
    catch (e: any) {
      logger.error('booking:book failed', e)
      return { success: false, error: e?.message || String(e) }
    }
  })

  // 批量预约（并发控制）
  ipcMain.handle('booking:book-multiple', async (_event, payload: { mode: 'playwright' | 'request', appointments: AppointmentManagement[], options?: any, concurrency?: number, persist?: boolean }) => {
    try {
      const mode = payload.mode
      // 复制并按优先级排序（数值越小优先级越高），同优先级下真实数据优先于占号数据
      const items = Array.isArray(payload.appointments) ? payload.appointments.slice() : []
      try {
        items.sort((a: any, b: any) => {
          const pa = Number.isFinite(a?.priority) ? Number(a.priority) : 1
          const pb = Number.isFinite(b?.priority) ? Number(b.priority) : 1
          if (pa !== pb)
            return pa - pb
          const ra = a?.is_placeholder ? 1 : 0
          const rb = b?.is_placeholder ? 1 : 0
          if (ra !== rb)
            return ra - rb // 真实(0) 在前，占号(1) 在后
          const ia = Number(a?.id || 0)
          const ib = Number(b?.id || 0)
          return ia - ib
        })
        logger.info('批次排序完成（按优先级与类型）', {
          total: items.length,
          head: items.slice(0, Math.min(5, items.length)).map((it: any) => ({ id: it?.id, priority: it?.priority, is_placeholder: !!it?.is_placeholder })),
        })
      }
      catch {}
      const total = items.length
      const cfg = getConfig() as any
      // 并发上限优先取 payload.concurrency，其次取配置 proxy.max_concurrent_sessions，兜底为3；硬上限防止极端数值
      const desired = Number(payload.concurrency || cfg?.proxy?.max_concurrent_sessions || 3)
      const limit = Math.max(1, Math.min(Number.isFinite(desired) ? desired : 3, 200))

      // 记录一次批量预约启动配置（结构化、脱敏）
      try {
        const proxyCfg = (cfg && cfg.proxy) ? cfg.proxy : {}
        const providers = Array.isArray(proxyCfg.providers) ? proxyCfg.providers : []
        const enabledProviders = providers.filter((p: any) => p && p.enabled !== false)
        const envFlags = {
          DEBUG_MONITOR_RANDOM_RELEASE: String(process.env.DEBUG_MONITOR_RANDOM_RELEASE || '') || undefined,
          DEBUG_MONITOR_MOCK_DATES: String(process.env.DEBUG_MONITOR_MOCK_DATES || '') || undefined,
          DEBUG_MONITOR_FORCE_FOUND: String(process.env.DEBUG_MONITOR_FORCE_FOUND || '') || undefined,
          DEBUG_BOOKING_FORCE_SUCCESS: String(process.env.DEBUG_BOOKING_FORCE_SUCCESS || '') || undefined,
          FORCE_ORIGINAL_HTTP: String(process.env.FORCE_ORIGINAL_HTTP || '') || undefined,
          NETWORK_USE_UNDICI: String(process.env.NETWORK_USE_UNDICI || '') || undefined,
        }
        const envForceOriginal = String(process.env.FORCE_ORIGINAL_HTTP || '').trim() === '1' || String(process.env.NETWORK_USE_UNDICI || '').trim() === '0'
        const effectiveUseUndici = !envForceOriginal && (cfg?.network?.use_undici !== false)
        logger.info('booking.start_config', {
          mode,
          total,
          concurrency: limit,
          options: payload.options || {},
          proxy: {
            enabled: !!proxyCfg.enabled,
            max_concurrent_sessions: Number(proxyCfg.max_concurrent_sessions || 0),
            rotate_on_errors_count: Array.isArray(proxyCfg.rotate_on_errors) ? proxyCfg.rotate_on_errors.length : 0,
            provider_count: enabledProviders.length,
          },
          network: {
            use_undici: cfg?.network?.use_undici !== false,
            request_timeout_seconds: Number(cfg?.network?.request_timeout_seconds || 0),
            max_connections_per_host: Number(cfg?.network?.max_connections_per_host || 0),
          },
          http_engine: effectiveUseUndici ? 'undici' : 'original',
          env: envFlags,
        })
      }
      catch {}

      let index = 0
      let running = 0
      let resolved = 0
      const results: Array<any> = []
      // 流量聚合（批次级）
      const trafficAgg: { totalUp: number, totalDown: number, perStep: Record<string, { up: number, down: number, count: number }> } = { totalUp: 0, totalDown: 0, perStep: {} }

      // 在本批次执行前，设置本批的并发上限；结束后恢复默认
      const previousDefault = cfg?.proxy?.max_concurrent_sessions || 2
      limiter.setDefaultLimit(previousDefault)
      limiter.setLimit(limit)

      // 创建批次 Job
      const prisma = getDatabase()
      const batchJobId = uuidv4()
      activeBatchJobId = batchJobId
      const now = Math.floor(Date.now() / 1000)
      try {
        await prisma.job.create({
          data: {
            id: batchJobId,
            type: 'booking_batch',
            mode,
            status: 'running',
            progress: 0,
            current_step: `准备并发（0/${total}）`,
            options: JSON.stringify({ concurrency: limit, ...(payload.options || {}) }),
            created_at: now,
            updated_at: now,
            started_at: now,
            result: JSON.stringify({ logs: [{ timestamp: now, phase: 'start', step: '开始批次预约', message: `并发=${limit}，总数=${total}` }] }),
          },
        })
        logger.info('job.created', { id: batchJobId, type: 'booking_batch', concurrency: limit, total })
      }
      catch (e) {
        logger.warn('创建 booking_batch job 失败', { error: String((e as any)?.message || e) })
      }

      await new Promise<void>((outerResolve) => {
        const launchNext = () => {
          while (running < limit && index < items.length) {
            if (isGlobalCancelRequested()) {
              // 全局取消：不再启动新的任务
              if (running === 0)
                outerResolve()
              return
            }
            const item = items[index++]
            running++
            ;(async () => {
              try {
                // 启动前抖动，避免同一秒洪峰触发限流（收紧至 50-150ms 提升整体吞吐）
                const jitter = Math.floor(50 + Math.random() * 100)
                await new Promise(r => setTimeout(r, jitter))

                // 预检：若该记录已成功（booked 或已有参考号），跳过实际预约，直接记为成功以避免重复处理
                try {
                  const appSvc = getAppointmentService()
                  const fresh = await appSvc.getAppointment(item.id)
                  if (fresh && (fresh.appointment_status === 'booked' || !!fresh.reference_number)) {
                    results.push({
                      id: item.id,
                      success: true,
                      error: undefined,
                      reference_number: fresh.reference_number || undefined,
                      selected_date: fresh.appointment_date || undefined,
                      selected_timeslot: fresh.appointment_time || undefined,
                      traffic: undefined,
                    })
                    return
                  }
                }
                catch {}

                // 使用 UserLogger 记录用户友好的日志
                const r = await svc.bookWith(mode, item, {
                  ...(payload.options || {}),
                  jobId: batchJobId, // 传递 jobId
                  onProgress: async ({ progress, step, timestamp }: { progress: number, step: string, timestamp: number }) => {
                    // 使用 UserLogger 记录用户友好的日志
                    await UserLogger.step(batchJobId, step, progress)

                    // 同时更新进度（保留原有逻辑）
                    try {
                      const ts = timestamp || Math.floor(Date.now() / 1000)
                      await prisma.job.update({ where: { id: batchJobId }, data: { progress, current_step: step, updated_at: ts } })
                    }
                    catch {}
                  },
                })
                // 可选持久化
                try {
                  if (payload.persist) {
                    const appSvc = getAppointmentService()
                    if (r.success) {
                      // 构建更新数据，只在有值时才更新日期和时间
                      const updateData: any = {
                        appointment_status: 'booked',
                        reference_number: r.referenceNumber,
                      }
                      // 只在有值时才更新日期和时间
                      if (r.selectedDate) {
                        updateData.appointment_date = r.selectedDate
                      }
                      if (r.selectedTimeslot) {
                        updateData.appointment_time = normalizeAppointmentTime(r.selectedTimeslot)
                      }
                      await appSvc.updateAppointment(item.id, updateData)
                      // 校验写库是否生效，若未生效则补救一次
                      try {
                        const fresh = await appSvc.getAppointment(item.id)
                        if (!(fresh && fresh.appointment_status === 'booked')) {
                          await appSvc.updateAppointment(item.id, updateData)
                        }
                      }
                      catch {}
                    }
                    else {
                      // 特判 512-E-0001：站点提示已存在同服务类型的预约 -> 直接记为 booked，避免后续重复执行
                      const alreadyBooked = typeof r.error === 'string' && /512-E-0001|已存在同服务类型的预约|already\s*book/i.test(r.error)
                      if (alreadyBooked) {
                        await appSvc.updateAppointment(item.id, { appointment_status: 'booked', error_message: r.error || '', retry_count: 0 } as any)
                      }
                      else {
                        // 获取当前预约记录以读取重试次数和错误类型
                        const currentAppointment = await appSvc.getAppointment(item.id)
                        const currentRetryCount = currentAppointment?.retry_count || 0
                        const currentErrorType = currentAppointment?.error_message || ''

                        // 判断错误类型
                        const isTimeslotFull = typeof r.error === 'string' && /timeslot_full|时段已满|時段已滿|时间段已满|時間段已滿|slot.*full|no.*slot|无可用时段|無可用時段|已满|512-e-0003|512-E-0003/i.test(r.error)
                        const isDuplicateLogin = typeof r.error === 'string' && /duplicate_login|重複登入|重覆登入|重复登入|512-e-0044|512-E-0044|系統而中斷|系统而中断/i.test(r.error)
                        const isPreferredDateMissed = typeof r.error === 'string' && /未命中偏好日期|未命中首选日期|preferred.*date.*miss/i.test(r.error)
                        const isNoAvailableDate = typeof r.error === 'string' && /70天内无可用日期|无可用日期|没有可用.*日期|no.*available.*date|当前没有可用的预约日期/i.test(r.error)
                        // 判断是否为网络/代理错误（应该重试，但连续3次后停止）
                        const isNetworkError = typeof r.error === 'string' && (
                          /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|ENETUNREACH|timeout|TIMEOUT|网络错误|网络异常|connection.*reset|connection.*refused|connection.*timeout|connect.*timeout|read.*ECONNRESET|Request timeout|Client\s+network\s+socket\s+disconnected\s+before\s+secure\s+TLS\s+connection\s+was\s+established|socket\s+hang\s+up|EPIPE|ERR_SSL/i.test(r.error)
                        )

                        // 检查是否连续出现相同错误类型
                        const isNoAvailableDateConsecutive = isNoAvailableDate && /无可用日期|no.*available.*date/i.test(currentErrorType)
                        const isNetworkErrorConsecutive = isNetworkError && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|timeout|网络错误|网络异常|socket\s+hang\s+up|EPIPE|ERR_SSL/i.test(currentErrorType)

                        // 对于无可用日期：连续3次后停止
                        if (isNoAvailableDate) {
                          const noDateRetryCount = isNoAvailableDateConsecutive ? currentRetryCount + 1 : 1
                          if (noDateRetryCount >= 3) {
                            // 连续3次无可用日期，标记为failed
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'failed',
                              error_message: r.error || '无可用日期（连续3次）',
                              retry_count: noDateRetryCount,
                            } as any)
                            logger.info('预约无可用日期（连续3次），标记为failed', { id: item.id, error: r.error, retryCount: noDateRetryCount })
                          }
                          else {
                            // 继续重试
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'pending',
                              error_message: r.error || '无可用日期（等待60秒后重试）',
                              retry_count: noDateRetryCount,
                              next_retry_at: new Date(Date.now() + 60_000),
                            } as any)
                            logger.info('预约无可用日期，保持pending状态以便重试（等待60秒）', { id: item.id, error: r.error, retryCount: noDateRetryCount })
                          }
                        }
                        // 对于网络/代理错误：连续3次后停止
                        else if (isNetworkError) {
                          const networkRetryCount = isNetworkErrorConsecutive ? currentRetryCount + 1 : 1
                          if (networkRetryCount >= 3) {
                            // 连续3次网络错误，标记为failed
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'failed',
                              error_message: r.error || '网络错误（连续3次）',
                              retry_count: networkRetryCount,
                            } as any)
                            logger.info('预约失败（网络错误连续3次），标记为failed', { id: item.id, error: r.error, retryCount: networkRetryCount })
                          }
                          else {
                            // 继续重试
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'pending',
                              error_message: r.error || '网络错误',
                              retry_count: networkRetryCount,
                            } as any)
                            logger.info('预约失败（网络错误），保持pending状态以便重试', { id: item.id, error: r.error, retryCount: networkRetryCount })
                          }
                        }
                        // 时间段已满：始终重试
                        else if (isTimeslotFull) {
                          await appSvc.updateAppointment(item.id, {
                            appointment_status: 'pending',
                            error_message: r.error || '时间段已满',
                            retry_count: 0, // 时间段已满不计数，因为这是正常的业务逻辑
                          } as any)
                          logger.info('预约失败（时间段已满），保持pending状态以便重试', { id: item.id, error: r.error })
                        }
                        // 重复登录/系统中断：始终重试
                        else if (isDuplicateLogin) {
                          await appSvc.updateAppointment(item.id, {
                            appointment_status: 'pending',
                            error_message: r.error || '服務因使用者重覆登入系統而中斷',
                            retry_count: 0, // 重复登录不计数，因为这是临时性的系统限制
                          } as any)
                          logger.info('预约失败（重复登录/系统中断），保持pending状态以便重试', { id: item.id, error: r.error })
                        }
                        // 未命中偏好日期：始终重试
                        else if (isPreferredDateMissed) {
                          await appSvc.updateAppointment(item.id, {
                            appointment_status: 'pending',
                            error_message: r.error || '未命中偏好日期',
                            retry_count: 0, // 未命中偏好日期不计数
                          } as any)
                          logger.info('预约未命中偏好日期，保持pending状态以便重试', { id: item.id, error: r.error })
                        }
                        // 其他错误：标记为failed
                        else {
                          await appSvc.updateAppointment(item.id, {
                            appointment_status: 'failed',
                            error_message: r.error || 'booking failed',
                            retry_count: currentRetryCount,
                          } as any)
                          logger.info('预约失败（其他错误），标记为failed', { id: item.id, error: r.error })
                        }
                      }
                    }
                  }
                }
                catch (e) {
                  logger.warn('booking:book-multiple persist failed', { id: item.id, error: String((e as any)?.message || e) })
                }
                // 聚合单条预约的流量
                try {
                  const t = (r as any)?.traffic
                  if (t) {
                    trafficAgg.totalUp += Number(t.totalUp || 0)
                    trafficAgg.totalDown += Number(t.totalDown || 0)
                    const ps = (t.perStep || {}) as Record<string, { up: number, down: number, count: number }>
                    for (const k of Object.keys(ps)) {
                      const prev = trafficAgg.perStep[k] || { up: 0, down: 0, count: 0 }
                      prev.up += Number(ps[k].up || 0)
                      prev.down += Number(ps[k].down || 0)
                      prev.count += Number(ps[k].count || 0)
                      trafficAgg.perStep[k] = prev
                    }
                  }
                }
                catch {}

                results.push({
                  id: item.id,
                  success: r.success,
                  error: r.error,
                  reference_number: (r as any)?.referenceNumber,
                  selected_date: (r as any)?.selectedDate,
                  selected_timeslot: (r as any)?.selectedTimeslot,
                  traffic: (r as any)?.traffic,
                })
                // 写一条调试日志确认本条 traffic
                try { logger.info('job.item.traffic', { id: item.id, up: (r as any)?.traffic?.totalUp || 0, down: (r as any)?.traffic?.totalDown || 0 }) }
                catch {}
              }
              catch (e: any) {
                results.push({ id: item.id, success: false, error: e?.message || String(e) })
              }
              finally {
                running--
                resolved++
                // 更新批次 Job 进度
                try {
                  const progress = total > 0 ? Math.floor((resolved / total) * 100) : 100
                  const step = `并发执行中（${resolved}/${total}）`
                  const ts = Math.floor(Date.now() / 1000)
                  await prisma.job.update({ where: { id: batchJobId }, data: { progress, current_step: step, updated_at: ts } })
                }
                catch (e) {
                  logger.warn('更新 booking_batch 进度失败', { error: String((e as any)?.message || e) })
                }
                if (resolved >= items.length)
                  outerResolve()
                else
                  launchNext()
              }
            })()
          }
        }

        launchNext()
      })

      // 批次完成，检查是否有pending状态的预约需要重试
      let allSuccess = false
      let retryRound = 0
      const maxRetryRounds = 1000 // 防止无限循环，实际应该会因为无可用日期而停止

      while (!allSuccess && retryRound < maxRetryRounds && !isGlobalCancelRequested()) {
        const successCount = results.filter(r => r.success).length

        // 检查数据库中pending状态的预约
        const appSvc = getAppointmentService()
        const pendingItems: AppointmentManagement[] = []

        for (const item of items) {
          try {
            const fresh = await appSvc.getAppointment(item.id)
            // 只重试pending状态的预约，跳过已成功和failed的
            // 检查是否已经成功（在results中或数据库中）
            const alreadySuccess = results.find(r => r.id === item.id && r.success) || (fresh && (fresh.appointment_status === 'booked' || !!fresh.reference_number))
            if (fresh && fresh.appointment_status === 'pending' && !alreadySuccess) {
              pendingItems.push(item)
            }
          }
          catch {}
        }

        // 如果没有pending状态的预约，检查是否所有都成功了
        if (pendingItems.length === 0) {
          allSuccess = successCount >= total
          break
        }

        // 如果有pending状态的预约，继续重试
        if (retryRound > 0) {
          // 重试前等待一段时间，避免频繁请求
          await new Promise(resolve => setTimeout(resolve, 2000))
        }

        retryRound++
        logger.info(`开始第 ${retryRound} 轮重试`, { pending: pendingItems.length, success: successCount, total })

        // 重试pending状态的预约
        await new Promise<void>((retryResolve) => {
          let retryIndex = 0
          let retryRunning = 0

          const launchRetryNext = () => {
            while (retryRunning < limit && retryIndex < pendingItems.length) {
              if (isGlobalCancelRequested()) {
                if (retryRunning === 0)
                  retryResolve()
                return
              }

              const item = pendingItems[retryIndex++]
              retryRunning++

              ;(async () => {
                try {
                  // 再次检查是否已成功（避免重复处理）
                  const fresh = await appSvc.getAppointment(item.id)
                  if (fresh && (fresh.appointment_status === 'booked' || !!fresh.reference_number)) {
                    // 更新results中的记录
                    const existingIndex = results.findIndex(r => r.id === item.id)
                    if (existingIndex >= 0) {
                      results[existingIndex] = {
                        id: item.id,
                        success: true,
                        error: undefined,
                        reference_number: fresh.reference_number || undefined,
                        selected_date: fresh.appointment_date || undefined,
                        selected_timeslot: fresh.appointment_time || undefined,
                        traffic: undefined,
                      }
                    }
                    else {
                      results.push({
                        id: item.id,
                        success: true,
                        error: undefined,
                        reference_number: fresh.reference_number || undefined,
                        selected_date: fresh.appointment_date || undefined,
                        selected_timeslot: fresh.appointment_time || undefined,
                        traffic: undefined,
                      })
                    }
                    return
                  }

                  // 启动前抖动 + 指数回退（系统繁忙）/ 固定冷却（无可用日期）
                  let backoff = 0
                  try {
                    const last = fresh || (await appSvc.getAppointment(item.id))
                    const lastErr = String(last?.error_message || '')
                    if (last && /(系统繁忙|系統繁忙|server\s*busy)/i.test(lastErr)) {
                      const rc = Math.max(1, Number(last.retry_count || 1))
                      const base = 300
                      backoff = Math.min(5000, base * 2 ** Math.max(0, rc - 1))
                    }
                    else if (last && /(无可用日期|沒有可用日期|70天内无可用日期|no\s*available\s*date)/i.test(lastErr)) {
                      // 无可用日期：固定等待60秒后再拉取，减少无效请求与占槽
                      backoff = 60_000
                    }
                  }
                  catch {}
                  const jitter = Math.floor(50 + Math.random() * 100)
                  const wait = backoff + jitter
                  if (wait > 0)
                    await new Promise(r => setTimeout(r, wait))

                  // 执行预约
                  const r = await svc.bookWith(mode, item, {
                    ...(payload.options || {}),
                    jobId: batchJobId,
                    onProgress: async ({ progress, step, timestamp }: { progress: number, step: string, timestamp: number }) => {
                      await UserLogger.step(batchJobId, step, progress)
                      try {
                        const ts = timestamp || Math.floor(Date.now() / 1000)
                        await prisma.job.update({ where: { id: batchJobId }, data: { progress, current_step: step, updated_at: ts } })
                      }
                      catch {}
                    },
                  })

                  // 持久化结果
                  try {
                    if (payload.persist) {
                      if (r.success) {
                        // 构建更新数据，只在有值时才更新日期和时间
                        const updateData: any = {
                          appointment_status: 'booked',
                          reference_number: r.referenceNumber,
                        }
                        // 只在有值时才更新日期和时间
                        if (r.selectedDate) {
                          updateData.appointment_date = r.selectedDate
                        }
                        if (r.selectedTimeslot) {
                          updateData.appointment_time = r.selectedTimeslot
                        }
                        await appSvc.updateAppointment(item.id, updateData)
                        // 校验写库是否生效
                        try {
                          const freshCheck = await appSvc.getAppointment(item.id)
                          if (!(freshCheck && freshCheck.appointment_status === 'booked')) {
                            await appSvc.updateAppointment(item.id, updateData)
                          }
                        }
                        catch {}
                      }
                      else {
                        // 特判 512-E-0001
                        const alreadyBooked = typeof r.error === 'string' && /512-E-0001|已存在同服务类型的预约|already\s*book/i.test(r.error)
                        if (alreadyBooked) {
                          await appSvc.updateAppointment(item.id, { appointment_status: 'booked', error_message: r.error || '', retry_count: 0 } as any)
                        }
                        else {
                          // 获取当前预约记录以读取重试次数和错误类型
                          const currentAppointment = await appSvc.getAppointment(item.id)
                          const currentRetryCount = currentAppointment?.retry_count || 0
                          const currentErrorType = currentAppointment?.error_message || ''

                          // 判断错误类型
                          const isTimeslotFull = typeof r.error === 'string' && /timeslot_full|时段已满|時段已滿|时间段已满|時間段已滿|slot.*full|no.*slot|无可用时段|無可用時段|已满|512-e-0003|512-E-0003/i.test(r.error)
                          const isDuplicateLogin = typeof r.error === 'string' && /duplicate_login|重複登入|重覆登入|重复登入|512-e-0044|512-E-0044|系統而中斷|系统而中断/i.test(r.error)
                          const isPreferredDateMissed = typeof r.error === 'string' && /未命中偏好日期|未命中首选日期|preferred.*date.*miss/i.test(r.error)
                          const isNoAvailableDate = typeof r.error === 'string' && /70天内无可用日期|无可用日期|没有可用.*日期|no.*available.*date|当前没有可用的预约日期/i.test(r.error)
                          // 判断是否为网络/代理错误（应该重试，但连续3次后停止）
                          const isNetworkError = typeof r.error === 'string' && (
                            /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|ENETUNREACH|timeout|TIMEOUT|网络错误|网络异常|connection.*reset|connection.*refused|connection.*timeout|connect.*timeout|read.*ECONNRESET|Request timeout|Client\s+network\s+socket\s+disconnected\s+before\s+secure\s+TLS\s+connection\s+was\s+established|socket\s+hang\s+up|EPIPE|ERR_SSL/i.test(r.error)
                          )

                          // 检查是否连续出现相同错误类型
                          const isNoAvailableDateConsecutive = isNoAvailableDate && /无可用日期|no.*available.*date/i.test(currentErrorType)
                          const isNetworkErrorConsecutive = isNetworkError && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|timeout|网络错误|网络异常|socket\s+hang\s+up|EPIPE|ERR_SSL/i.test(currentErrorType)

                          // 对于无可用日期：连续3次后停止
                          if (isNoAvailableDate) {
                            const noDateRetryCount = isNoAvailableDateConsecutive ? currentRetryCount + 1 : 1
                            if (noDateRetryCount >= 3) {
                              // 连续3次无可用日期，标记为failed
                              await appSvc.updateAppointment(item.id, {
                                appointment_status: 'failed',
                                error_message: r.error || '无可用日期（连续3次）',
                                retry_count: noDateRetryCount,
                              } as any)
                              logger.info('预约无可用日期（连续3次），标记为failed', { id: item.id, error: r.error, retryCount: noDateRetryCount })
                            }
                            else {
                              // 继续重试
                              await appSvc.updateAppointment(item.id, {
                                appointment_status: 'pending',
                                error_message: r.error || '无可用日期（等待60秒后重试）',
                                retry_count: noDateRetryCount,
                                next_retry_at: new Date(Date.now() + 60_000),
                              } as any)
                              logger.info('预约无可用日期，保持pending状态以便重试（等待60秒）', { id: item.id, error: r.error, retryCount: noDateRetryCount })
                            }
                          }
                          // 对于网络/代理错误：连续3次后停止
                          else if (isNetworkError) {
                            const networkRetryCount = isNetworkErrorConsecutive ? currentRetryCount + 1 : 1
                            if (networkRetryCount >= 3) {
                              // 连续3次网络错误，标记为failed
                              await appSvc.updateAppointment(item.id, {
                                appointment_status: 'failed',
                                error_message: r.error || '网络错误（连续3次）',
                                retry_count: networkRetryCount,
                              } as any)
                              logger.info('预约失败（网络错误连续3次），标记为failed', { id: item.id, error: r.error, retryCount: networkRetryCount })
                            }
                            else {
                              // 继续重试
                              await appSvc.updateAppointment(item.id, {
                                appointment_status: 'pending',
                                error_message: r.error || '网络错误',
                                retry_count: networkRetryCount,
                              } as any)
                              logger.info('预约失败（网络错误），保持pending状态以便重试', { id: item.id, error: r.error, retryCount: networkRetryCount })
                            }
                          }
                          // 时间段已满：始终重试
                          else if (isTimeslotFull) {
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'pending',
                              error_message: r.error || '时间段已满',
                              retry_count: 0, // 时间段已满不计数
                            } as any)
                            logger.info('预约失败（时间段已满），保持pending状态以便重试', { id: item.id, error: r.error })
                          }
                          // 重复登录/系统中断：始终重试
                          else if (isDuplicateLogin) {
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'pending',
                              error_message: r.error || '服務因使用者重覆登入系統而中斷',
                              retry_count: 0, // 重复登录不计数，因为这是临时性的系统限制
                            } as any)
                            logger.info('预约失败（重复登录/系统中断），保持pending状态以便重试', { id: item.id, error: r.error })
                          }
                          // 未命中偏好日期：始终重试
                          else if (isPreferredDateMissed) {
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'pending',
                              error_message: r.error || '未命中偏好日期',
                              retry_count: 0, // 未命中偏好日期不计数
                            } as any)
                            logger.info('预约未命中偏好日期，保持pending状态以便重试', { id: item.id, error: r.error })
                          }
                          // 其他错误：标记为failed
                          else {
                            await appSvc.updateAppointment(item.id, {
                              appointment_status: 'failed',
                              error_message: r.error || 'booking failed',
                              retry_count: currentRetryCount,
                            } as any)
                            logger.info('预约失败（其他错误），标记为failed', { id: item.id, error: r.error })
                          }
                        }
                      }
                    }
                  }
                  catch (e) {
                    logger.warn('booking:book-multiple retry persist failed', { id: item.id, error: String((e as any)?.message || e) })
                  }

                  // 更新流量统计
                  try {
                    const t = (r as any)?.traffic
                    if (t) {
                      trafficAgg.totalUp += Number(t.totalUp || 0)
                      trafficAgg.totalDown += Number(t.totalDown || 0)
                      const ps = (t.perStep || {}) as Record<string, { up: number, down: number, count: number }>
                      for (const k of Object.keys(ps)) {
                        const prev = trafficAgg.perStep[k] || { up: 0, down: 0, count: 0 }
                        prev.up += Number(ps[k].up || 0)
                        prev.down += Number(ps[k].down || 0)
                        prev.count += Number(ps[k].count || 0)
                        trafficAgg.perStep[k] = prev
                      }
                    }
                  }
                  catch {}

                  // 更新results
                  const existingIndex = results.findIndex(res => res.id === item.id)
                  if (existingIndex >= 0) {
                    results[existingIndex] = {
                      id: item.id,
                      success: r.success,
                      error: r.error,
                      reference_number: (r as any)?.referenceNumber,
                      selected_date: (r as any)?.selectedDate,
                      selected_timeslot: (r as any)?.selectedTimeslot,
                      traffic: (r as any)?.traffic,
                    }
                  }
                  else {
                    results.push({
                      id: item.id,
                      success: r.success,
                      error: r.error,
                      reference_number: (r as any)?.referenceNumber,
                      selected_date: (r as any)?.selectedDate,
                      selected_timeslot: (r as any)?.selectedTimeslot,
                      traffic: (r as any)?.traffic,
                    })
                  }
                }
                catch (e: any) {
                  const existingIndex = results.findIndex(res => res.id === item.id)
                  if (existingIndex >= 0) {
                    results[existingIndex] = { id: item.id, success: false, error: e?.message || String(e) }
                  }
                  else {
                    results.push({ id: item.id, success: false, error: e?.message || String(e) })
                  }
                }
                finally {
                  retryRunning--
                  if (retryIndex >= pendingItems.length && retryRunning <= 0)
                    retryResolve()
                  else
                    launchRetryNext()
                }
              })()
            }
          }

          launchRetryNext()
        })

        // 检查是否所有都成功了
        const currentSuccessCount = results.filter(r => r.success).length
        allSuccess = currentSuccessCount >= total

        // 如果没有更多pending状态的预约，退出循环
        if (pendingItems.length === 0)
          break
      }

      // 批次完成，恢复默认限流
      limiter.setLimit(null)
      clearGlobalCancel()

      const successCount = results.filter(r => r.success).length
      // 结束批次 Job
      try {
        const end = Math.floor(Date.now() / 1000)
        const row = await prisma.job.findUnique({ where: { id: batchJobId }, select: { result: true } })
        const prev = row?.result ? JSON.parse(row.result as any) : {}
        const logs = Array.isArray(prev.logs) ? prev.logs : []
        logs.push({ timestamp: end, phase: successCount > 0 ? 'success' : 'error', step: '批次结束', message: `成功 ${successCount}/${total}; 流量 up=${trafficAgg.totalUp} down=${trafficAgg.totalDown}; 重试轮次 ${retryRound}` })

        // 批次级指标与清单
        const batchMetrics = {
          durationMs: Math.max(0, (end - now) * 1000),
          bytesSent: Number(trafficAgg.totalUp || 0),
          bytesReceived: Number(trafficAgg.totalDown || 0),
        }
        const appointments = items.map(it => ({
          id: it.id,
          user_passport: it.passport_number,
          user_name: it.full_name,
          service_type: it.service_type,
          office_code: it.office_code,
        }))

        await prisma.job.update({
          where: { id: batchJobId },
          data: {
            status: successCount >= total ? 'success' : (successCount > 0 ? 'partial' : 'failed'),
            progress: 100,
            current_step: successCount >= total ? `完成（成功 ${successCount}/${total}）` : (successCount > 0 ? `部分完成（成功 ${successCount}/${total}）` : '完成（全部失败）'),
            updated_at: end,
            completed_at: end,
            result: JSON.stringify({ total: results.length, success: successCount, failed: results.length - successCount, retryRounds: retryRound, traffic: trafficAgg, metrics: batchMetrics, appointments, results, logs }),
          },
        })
        logger.info('job.updated', { id: batchJobId, traffic_totalUp: trafficAgg.totalUp, traffic_totalDown: trafficAgg.totalDown, retryRounds: retryRound })
      }
      catch (e) {
        logger.warn('完成 booking_batch job 更新失败', { error: String((e as any)?.message || e) })
      }
      finally {
        activeBatchJobId = null
      }

      return { success: true, data: { total: results.length, success: successCount, failed: results.length - successCount, retryRounds: retryRound, results, jobId: batchJobId } }
    }
    catch (e: any) {
      logger.error('booking:book-multiple failed', e)
      return { success: false, error: e?.message || String(e) }
    }
  })

  // 停止当前所有预约（手动取消）
  ipcMain.handle('booking:stop', async () => {
    try {
      if (isStopping)
        return { success: true, data: { cancelled: 0 }, message: 'stopping' }
      isStopping = true
      // 标记全局取消，阻止 book-multiple 新任务继续启动
      requestGlobalCancel()
      const before = getActiveEngineCount()
      await cancelAllEngines()
      // 额外：停止监控与无人值守
      try {
        const mon = getBookingMonitor()
        if (mon?.isRunning?.())
          await mon.stop()
      }
      catch {}
      try {
        // 停止无人值守循环
        const as = getAutomationService()
        await as.stopAutomation()
      }
      catch {}
      // 清理注册表，避免残留
      try { clearRegistry() }
      catch {}
      // 标记批次 Job 为已取消
      try {
        if (activeBatchJobId) {
          const prisma = getDatabase()
          const ts = Math.floor(Date.now() / 1000)
          const row = await prisma.job.findUnique({ where: { id: activeBatchJobId }, select: { result: true } })
          const prev = row?.result ? JSON.parse(row.result as any) : {}
          const logs = Array.isArray(prev.logs) ? prev.logs : []
          logs.push({ timestamp: ts, phase: 'warn', step: '已取消', message: '用户停止批次预约' })
          await prisma.job.update({ where: { id: activeBatchJobId }, data: { status: 'cancelled', updated_at: ts, completed_at: ts, current_step: '已取消', result: JSON.stringify({ ...prev, logs }) } })
          activeBatchJobId = null
        }
        // 同时终结所有 type=monitor 的运行中 Job（兜底）
        try {
          const prisma = getDatabase()
          const runningMonitors = await prisma.job.findMany({ where: { type: 'monitor', status: 'running' }, select: { id: true } })
          const ts = Math.floor(Date.now() / 1000)
          for (const j of runningMonitors) {
            try {
              const row = await prisma.job.findUnique({ where: { id: j.id }, select: { result: true } })
              const prev = row?.result ? JSON.parse(row.result as any) : {}
              const logs = Array.isArray(prev.logs) ? prev.logs : []
              logs.push({ timestamp: ts, phase: 'warn', step: '停止监控', message: '停止预约时一并停止监控' })
              await prisma.job.update({ where: { id: j.id }, data: { status: 'cancelled', updated_at: ts, completed_at: ts, current_step: '已停止', result: JSON.stringify({ ...prev, logs }) } })
            }
            catch {}
          }
        }
        catch {}
      }
      catch (e) {
        logger.warn('更新 booking_batch 取消状态失败', { error: String((e as any)?.message || e) })
      }
      return { success: true, data: { cancelled: before } }
    }
    catch (e: any) {
      logger.warn('booking:stop failed', { error: String(e?.message || e) })
      return { success: false, error: e?.message || String(e) }
    }
    finally {
      isStopping = false
    }
  })

  // 预约状态查询：返回当前活跃引擎数量与是否处于停止中
  ipcMain.handle('booking:status', async () => {
    try {
      return { success: true, data: { active: getActiveEngineCount(), stopping: isStopping, cancelled: isGlobalCancelRequested() } }
    }
    catch (e: any) {
      logger.warn('booking:status failed', { error: String(e?.message || e) })
      return { success: false, error: e?.message || String(e) }
    }
  })

  // 获取Job的日志
  ipcMain.handle('booking:getJobLogs', async (_event, jobId: string) => {
    try {
      const prisma = getDatabase()
      const job = await prisma.job.findUnique({
        where: { id: jobId },
        select: { result: true, status: true },
      })
      if (!job) {
        return { success: false, error: 'Job not found' }
      }
      const result = job.result ? JSON.parse(job.result as any) : {}
      const logs = Array.isArray(result.logs) ? result.logs : []
      return { success: true, data: { logs, status: job.status } }
    }
    catch (e: any) {
      logger.warn('booking:getJobLogs failed', { error: String(e?.message || e) })
      return { success: false, error: e?.message || String(e) }
    }
  })

  // 启动预约监控（单号探测，无预约则按间隔轮询，有预约则并发触发）
  ipcMain.handle('booking-monitor:start', async (_event, payload: { monitor: AppointmentManagement, candidates: AppointmentManagement[], concurrency: number, intervalMs?: number, windowDays?: number, options?: any }) => {
    try {
      // 将监控执行器并发与前端预约并发保持一致
      try {
        const cfg = getConfig()
        const desired = Number(payload?.concurrency || cfg?.proxy?.max_concurrent_sessions || 5)
        const limit = Math.max(1, Math.min(Number.isFinite(desired) ? desired : 5, 200))
        const executor = getMonitoringTaskExecutor()
        executor.setConcurrency(limit)
      }
      catch {}

      const mon = getBookingMonitor()
      if (mon.isRunning())
        return { success: false, error: 'monitor already running' }
      // 校验：monitor 记录必须为监控类型
      try {
        if (!payload?.monitor || (payload as any).monitor.is_monitor !== true) {
          return { success: false, error: '监控启动失败：监控记录必须为"监控类型"数据' }
        }
      }
      catch {}
      // 挂接 reporter，将监控"任务日志"写入一条持久 Job（type=monitor）
      const prisma = getDatabase()
      const now = Math.floor(Date.now() / 1000)
      const monitorJobId = `mon_${Date.now()}`
      try {
        await prisma.job.create({ data: { id: monitorJobId, type: 'monitor', mode: 'monitor', status: 'running', progress: 0, current_step: '启动监控', created_at: now, updated_at: now, started_at: now, result: JSON.stringify({ logs: [] }) } })
      }
      catch {}
      mon.setReporter(async (u) => {
        try {
          const ts = u.timestamp || Math.floor(Date.now() / 1000)
          const row = await prisma.job.findUnique({ where: { id: monitorJobId }, select: { result: true } })
          const prev = row?.result ? JSON.parse(row.result as any) : {}
          const logs = Array.isArray(prev.logs) ? prev.logs : []
          logs.push({ timestamp: ts, phase: u.phase || 'info', step: u.step || '', message: u.message || '' })
          // 新增：聚合结构化放号记录 releases
          let releases = Array.isArray((prev as any).releases) ? (prev as any).releases : []
          if ((u as any)?.extra?.release) {
            releases.push((u as any).extra.release)
            releases = releases.slice(-200)
          }
          await prisma.job.update({ where: { id: monitorJobId }, data: { progress: Math.max(0, Math.min(100, Math.floor(u.progress || 0))), current_step: u.step || '监控', updated_at: ts, result: JSON.stringify({ ...prev, logs: logs.slice(-500), releases }) } })
        }
        catch {}
      })
      // 记录一次监控启动配置（结构化、脱敏）
      try {
        const execStatus = getMonitoringTaskExecutor().getStatus()
        const cfg = getConfig() as any
        const proxyCfg = (cfg?.proxy) || {}
        const envFlags = {
          DEBUG_MONITOR_RANDOM_RELEASE: String(process.env.DEBUG_MONITOR_RANDOM_RELEASE || '') || undefined,
          DEBUG_MONITOR_MOCK_DATES: String(process.env.DEBUG_MONITOR_MOCK_DATES || '') || undefined,
          DEBUG_MONITOR_FORCE_FOUND: String(process.env.DEBUG_MONITOR_FORCE_FOUND || '') || undefined,
          DEBUG_BOOKING_FORCE_SUCCESS: String(process.env.DEBUG_BOOKING_FORCE_SUCCESS || '') || undefined,
          FORCE_ORIGINAL_HTTP: String(process.env.FORCE_ORIGINAL_HTTP || '') || undefined,
          NETWORK_USE_UNDICI: String(process.env.NETWORK_USE_UNDICI || '') || undefined,
        }
        const envForceOriginal = String(process.env.FORCE_ORIGINAL_HTTP || '').trim() === '1' || String(process.env.NETWORK_USE_UNDICI || '').trim() === '0'
        const effectiveUseUndici = !envForceOriginal && (cfg?.network?.use_undici !== false)
        logger.info('monitor.start_config', {
          monitor_id: payload?.monitor?.id,
          candidates_count: Array.isArray(payload?.candidates) ? payload.candidates.length : 0,
          requested_concurrency: Number(payload?.concurrency || 0),
          executor_concurrency: Number(execStatus?.concurrency || 0),
          intervalMs: Number(payload?.intervalMs || 0),
          windowDays: Number(payload?.windowDays || 0),
          options: payload?.options || {},
          proxy: {
            enabled: !!proxyCfg.enabled,
            max_concurrent_sessions: Number(proxyCfg.max_concurrent_sessions || 0),
          },
          http_engine: effectiveUseUndici ? 'undici' : 'original',
          env: envFlags,
        })
      }
      catch {}

      await mon.start(payload)
      return { success: true }
    }
    catch (e: any) {
      logger.error('booking-monitor:start failed', e)
      return { success: false, error: e?.message || String(e) }
    }
  })

  ipcMain.handle('booking-monitor:stop', async () => {
    try {
      const mon = getBookingMonitor()
      await mon.stop()
      // 标记最近一条 monitor Job 为已取消/完成
      try {
        const prisma = getDatabase()
        const latest = await prisma.job.findFirst({ where: { type: 'monitor', status: 'running' }, orderBy: { created_at: 'desc' }, select: { id: true } })
        if (latest?.id) {
          const ts = Math.floor(Date.now() / 1000)
          const row = await prisma.job.findUnique({ where: { id: latest.id }, select: { result: true } })
          const prev = row?.result ? JSON.parse(row.result as any) : {}
          const logs = Array.isArray(prev.logs) ? prev.logs : []
          logs.push({ timestamp: ts, phase: 'warn', step: '停止监控', message: '手动停止监控' })
          await prisma.job.update({ where: { id: latest.id }, data: { status: 'cancelled', updated_at: ts, completed_at: ts, current_step: '已停止', result: JSON.stringify({ ...prev, logs }) } })
        }
      }
      catch {}
      return { success: true }
    }
    catch (e: any) {
      logger.warn('booking-monitor:stop failed', { error: e?.message || String(e) })
      return { success: false, error: e?.message || String(e) }
    }
  })

  /**
   * 修复缺失的预约数据（参考编号、预约日期、预约时间）
   * 查找所有状态为booked但缺少这些信息的记录，通过参考编号或证件号/执照号查询详情并更新
   */
  ipcMain.handle('booking:repair-missing-data', async () => {
    const logger = getLogger()
    const appSvc = getAppointmentService()
    const prisma = getDatabase()

    let appointments: any[] = []
    try {
      // 查找所有状态为booked但缺少参考编号或日期时间的记录
      appointments = await prisma.appointment_management.findMany({
        where: {
          appointment_status: 'booked',
          OR: [
            { reference_number: null },
            { reference_number: '' },
            { appointment_date: null },
            { appointment_time: null },
          ],
        },
        select: {
          id: true,
          reference_number: true,
          appointment_date: true,
          appointment_time: true,
          passport_number: true,
          license_number: true,
          full_name: true,
        },
      })

      logger.info('开始修复缺失的预约数据', { count: appointments.length })

      if (appointments.length === 0) {
        return { success: true, repaired: 0, skipped: 0, failed: 0, message: '没有需要修复的记录' }
      }

      let repaired = 0
      let skipped = 0
      let failed = 0

      // 使用封装好的 TdabsRequestService（静态导入）
      const tsvc = getTdabsRequestService()

      // 并发修复：使用 Promise.allSettled 并发处理，限制并发数为 3
      const concurrency = 3
      const results: Array<{ repaired: boolean, skipped: boolean, failed: boolean }> = []

      for (let i = 0; i < appointments.length; i += concurrency) {
        const batch = appointments.slice(i, i + concurrency)
        const batchPromises = batch.map(async (apt) => {
          try {
            const updateData: any = {}

            // 优先通过参考编号（若已有）
            if (apt.reference_number && String(apt.reference_number).trim()) {
              const refNo = String(apt.reference_number).trim()
              const ref6 = refNo.slice(-6)
              const byRef = await tsvc.queryByRef({ refLast6: ref6 })
              if (byRef?.success && byRef.data?.parsed) {
                const info = byRef.data.parsed
                if ((!apt.reference_number || !String(apt.reference_number).trim()) && info.reference_number)
                  updateData.reference_number = info.reference_number
                if (!apt.appointment_date && info.appointment_date)
                  updateData.appointment_date = info.appointment_date
                if (!apt.appointment_time && info.appointment_time)
                  updateData.appointment_time = normalizeAppointmentTime(info.appointment_time)
              }
              else if (byRef?.error === 'NO_RECORD') {
                // 参考号不存在于远端，跳过
                return { repaired: false, skipped: true, failed: false }
              }
              else {
                logger.warn('按参考号查询失败，将尝试按证件/执照', { id: apt.id, error: byRef?.error })
              }
            }

            // 若仍无可写回数据，改用证件/执照查询（需要 license + hkid/passport 二选一）
            if (Object.keys(updateData).length === 0) {
              const hasLicense = !!(apt.license_number && String(apt.license_number).trim())
              const hasId = !!(apt.passport_number && String(apt.passport_number).trim())
              if (!hasLicense && !hasId)
                return { repaired: false, skipped: true, failed: false }

              const byId = await tsvc.queryById({
                passport: hasId ? String(apt.passport_number).trim() : undefined,
                license: hasLicense ? String(apt.license_number).trim() : undefined,
              })
              if (byId?.success && byId.data?.parsed) {
                const info = byId.data.parsed
                if ((!apt.reference_number || !String(apt.reference_number).trim()) && info.reference_number)
                  updateData.reference_number = info.reference_number
                if (!apt.appointment_date && info.appointment_date)
                  updateData.appointment_date = info.appointment_date
                if (!apt.appointment_time && info.appointment_time)
                  updateData.appointment_time = normalizeAppointmentTime(info.appointment_time)
              }
              else if (byId?.error === 'NO_RECORD') {
                return { repaired: false, skipped: true, failed: false }
              }
              else {
                logger.warn('按证件/执照查询失败', { id: apt.id, error: byId?.error })
                return { repaired: false, skipped: false, failed: true }
              }
            }

            // 应用更新
            if (Object.keys(updateData).length > 0) {
              await appSvc.updateAppointment(apt.id, updateData)
              logger.info('成功修复预约数据', { id: apt.id, updated: Object.keys(updateData) })
              return { repaired: true, skipped: false, failed: false }
            }
            else {
              logger.info('预约数据无需更新', { id: apt.id })
              return { repaired: false, skipped: true, failed: false }
            }
          }
          catch (e: any) {
            logger.error('修复预约数据失败', { id: apt.id, error: e?.message || String(e) })
            return { repaired: false, skipped: false, failed: true }
          }
        })

        // 等待当前批次完成
        const batchResults = await Promise.allSettled(batchPromises)
        for (const result of batchResults) {
          if (result.status === 'fulfilled') {
            results.push(result.value)
          }
          else {
            // Promise 被拒绝，视为失败
            results.push({ repaired: false, skipped: false, failed: true })
            logger.error('修复预约数据 Promise 被拒绝', { error: result.reason })
          }
        }

        // 批次间添加小延迟，避免请求过于频繁
        if (i + concurrency < appointments.length) {
          await new Promise(resolve => setTimeout(resolve, 500))
        }
      }

      // 统计结果
      for (const result of results) {
        if (result.repaired)
          repaired++
        else if (result.skipped)
          skipped++
        else if (result.failed)
          failed++
      }

      return {
        success: true,
        repaired,
        skipped,
        failed,
        total: appointments.length,
        message: `修复完成：成功 ${repaired} 条，跳过 ${skipped} 条，失败 ${failed} 条`,
      }
    }
    catch (e: any) {
      logger.error('修复缺失预约数据失败', { error: e?.message || String(e) })
      return {
        success: false,
        error: e?.message || String(e),
        repaired: 0,
        skipped: 0,
        failed: 0,
        total: appointments?.length || 0,
        message: `修复失败: ${e?.message || String(e)}`,
      }
    }
  })

  logger.info('✅ Booking IPC handlers registered')
}
